/*
 * SubSonic - http://subsonicproject.com
 * 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an 
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
*/

using System;
using System.Data;
using System.Collections;
using System.ComponentModel;

namespace SubSonic
{
    public enum ConstraintType
    {
        Where,
        And,
        Or
    }

    /// <summary>
    /// Summary for the Constraint class
    /// </summary>
    public class Constraint
    {
        /// <summary>
        /// 
        /// </summary>
        public SqlQuery query;


        #region Factory methods

        public static Constraint Where(string columnName)
        {
            Constraint c = new Constraint();
            c.columnName = columnName;
            c.Condition = ConstraintType.Where;
            return c;
        }

        public static Constraint And(string columnName)
        {
            Constraint c = new Constraint();
            c.columnName = columnName;
            c.Condition = ConstraintType.And;
            return c;
        }

        public static Constraint Or(string columnName)
        {
            Constraint c = new Constraint();
            c.columnName = columnName;
            c.Condition = ConstraintType.Or;
            return c;
        }

        #endregion


        #region props

        private string columnName;
        private Comparison comp;
        private ConstraintType condition = ConstraintType.Where;
        private DbType dbType;
        private string parameterName;
        private object paramValue;

        private string tableName;

        /// <summary>
        /// Gets or sets the condition.
        /// </summary>
        /// <value>The condition.</value>
        public ConstraintType Condition
        {
            get { return condition; }
            set { condition = value; }
        }

        /// <summary>
        /// Gets or sets the name of the table.
        /// </summary>
        /// <value>The name of the table.</value>
        public string TableName
        {
            get { return tableName; }
            set { tableName = value; }
        }

        /// <summary>
        /// Gets or sets the name of the column.
        /// </summary>
        /// <value>The name of the column.</value>
        public string ColumnName
        {
            get { return columnName; }
            set { columnName = value; }
        }

        /// <summary>
        /// Gets or sets the comparison.
        /// </summary>
        /// <value>The comparison.</value>
        public Comparison Comparison
        {
            get { return comp; }
            set { comp = value; }
        }

        /// <summary>
        /// Gets or sets the parameter value.
        /// </summary>
        /// <value>The parameter value.</value>
        public object ParameterValue
        {
            get { return paramValue; }
            set { paramValue = value; }
        }

        private object _startValue;

        public object StartValue
        {
            get { return _startValue; }
            set { _startValue = value; }
        }

        private object _endValue;

        public object EndValue
        {
            get { return _endValue; }
            set { _endValue = value; }
        }

        private IEnumerable _inValues;

        public IEnumerable InValues
        {
            get { return _inValues; }
            set { _inValues = value; }
        }

        private Select _inSelect;

        public Select InSelect {
            get { return _inSelect; }
            set { _inSelect = value; }
        }
	

        /// <summary>
        /// Gets or sets the name of the parameter.
        /// </summary>
        /// <value>The name of the parameter.</value>
        public string ParameterName
        {
            get { return parameterName ?? ColumnName; }
            set { parameterName = value; }
        }

        /// <summary>
        /// Gets or sets the type of the db.
        /// </summary>
        /// <value>The type of the db.</value>
        public DbType DbType
        {
            get { return dbType; }
            set { dbType = value; }
        }

        /// <summary>
        /// Gets the comparison operator.
        /// </summary>
        /// <param name="comp">The comp.</param>
        /// <returns></returns>
        public static string GetComparisonOperator(Comparison comp)
        {
            string sOut;
            switch(comp)
            {
                case Comparison.Blank:
                    sOut = SqlComparison.BLANK;
                    break;
                case Comparison.GreaterThan:
                    sOut = SqlComparison.GREATER;
                    break;
                case Comparison.GreaterOrEquals:
                    sOut = SqlComparison.GREATER_OR_EQUAL;
                    break;
                case Comparison.LessThan:
                    sOut = SqlComparison.LESS;
                    break;
                case Comparison.LessOrEquals:
                    sOut = SqlComparison.LESS_OR_EQUAL;
                    break;
                case Comparison.Like:
                    sOut = SqlComparison.LIKE;
                    break;
                case Comparison.NotEquals:
                    sOut = SqlComparison.NOT_EQUAL;
                    break;
                case Comparison.NotLike:
                    sOut = SqlComparison.NOT_LIKE;
                    break;
                case Comparison.Is:
                    sOut = SqlComparison.IS;
                    break;
                case Comparison.IsNot:
                    sOut = SqlComparison.IS_NOT;
                    break;
                case Comparison.OpenParen:
                    sOut = "(";
                    break;
                case Comparison.CloseParen:
                    sOut = ")";
                    break;
                case Comparison.In:
                    sOut = " IN ";
                    break;
                case Comparison.NotIn:
                    sOut = " NOT IN ";
                    break;
                default:
                    sOut = SqlComparison.EQUAL;
                    break;
            }
            return sOut;
        }

        #endregion


        [EditorBrowsable(EditorBrowsableState.Never)]
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public SqlQuery Like(string val)
        {
            Comparison = Comparison.Like;
            ParameterValue = val;
            query.Constraints.Add(this);

            return query;
        }

        public SqlQuery NotLike(string val)
        {
            Comparison = Comparison.NotLike;
            ParameterValue = val;
            query.Constraints.Add(this);

            return query;
        }

        /// <summary>
        /// Determines whether [is greater than] [the specified val].
        /// </summary>
        /// <param name="val">The val.</param>
        /// <returns></returns>
        public SqlQuery IsGreaterThan(object val)
        {
            Comparison = Comparison.GreaterThan;
            ParameterValue = val;

            query.Constraints.Add(this);

            return query;
        }

        /// <summary>
        /// Determines whether [is greater than] [the specified val].
        /// </summary>
        /// <param name="val">The val.</param>
        /// <returns></returns>
        public SqlQuery IsGreaterThanOrEqualTo(object val)
        {
            Comparison = Comparison.GreaterOrEquals;
            ParameterValue = val;

            query.Constraints.Add(this);

            return query;
        }


        /// <summary>
        /// Specifies a SQL IN statement using a nested Select statement
        /// </summary>
        /// <param name="vals">Value array</param>
        /// <returns></returns>
        public SqlQuery In(Select selectQuery) {

            //validate that there is only one column in the columnlist
            if (selectQuery.SelectColumnList.Length == 0 || selectQuery.SelectColumnList.Length>1)
                throw new SqlQueryException("You must specify a column to return for the IN to be valid. Use Select(\"column\") to do this");

            this.InSelect = selectQuery;
            
            Comparison = Comparison.In;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Specifies a SQL IN statement
        /// </summary>
        /// <param name="vals">Value array</param>
        /// <returns></returns>
        public SqlQuery In(IEnumerable vals)
        {
            InValues = vals;
            Comparison = Comparison.In;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Specifies a SQL IN statement
        /// </summary>
        /// <param name="vals">Value array</param>
        /// <returns></returns>
        public SqlQuery In(params object[] vals)
        {
            //this is trickery, since every time we send in a Select query, it will call this method
            //so we need to evaluate it, and call In(Select)
            //I don't like this hack, but don't see a way around it

            if (vals.Length >0) {
                if (vals[0].ToString().StartsWith("SELECT")) {
                    Select s = (Select)vals[0];
                    query = In(s);
                } else {
                    InValues = vals;
                    Comparison = Comparison.In;
                    query.Constraints.Add(this);

                }
            }
            
            return query;
        }
        /// <summary>
        /// Specifies a SQL IN statement using a nested Select statement
        /// </summary>
        /// <param name="vals">Value array</param>
        /// <returns></returns>
        public SqlQuery NotIn(Select selectQuery) {

            //validate that there is only one column in the columnlist
            if (selectQuery.SelectColumnList.Length == 0 || selectQuery.SelectColumnList.Length > 1)
                throw new SqlQueryException("You must specify a column to return for the IN to be valid. Use Select(\"column\") to do this");

            this.InSelect = selectQuery;

            Comparison = Comparison.NotIn;
            query.Constraints.Add(this);
            return query;
        }
        /// <summary>
        /// Specifies a SQL Not IN statement
        /// </summary>
        /// <param name="vals">Value array</param>
        /// <returns></returns>
        public SqlQuery NotIn(IEnumerable vals)
        {
            InValues = vals;
            Comparison = Comparison.NotIn;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Specifies a SQL NOT IN statement
        /// </summary>
        /// <param name="vals">Value array</param>
        /// <returns></returns>
        public SqlQuery NotIn(params object[] vals)
        {
            if (vals.Length > 0) {
                if (vals[0].ToString().StartsWith("SELECT")) {
                    Select s = (Select)vals[0];
                    query = NotIn(s);
                } else {
                    InValues = vals;
                    Comparison = Comparison.NotIn;
                    query.Constraints.Add(this);

                }
            }
            return query;
        }

        /// <summary>
        /// Determines whether [is less than] [the specified val].
        /// </summary>
        /// <param name="val">The val.</param>
        /// <returns></returns>
        public SqlQuery IsLessThan(object val)
        {
            Comparison = Comparison.LessThan;
            paramValue = val;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Determines whether [is less than] [the specified val].
        /// </summary>
        /// <param name="val">The val.</param>
        /// <returns></returns>
        public SqlQuery IsLessThanOrEqualTo(object val)
        {
            Comparison = Comparison.LessOrEquals;
            paramValue = val;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Determines whether [is not null] [the specified val].
        /// </summary>
        /// <returns></returns>
        public SqlQuery IsNotNull()
        {
            Comparison = Comparison.IsNot;
            paramValue = DBNull.Value;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Determines whether the specified val is null.
        /// </summary>
        /// <returns></returns>
        public SqlQuery IsNull()
        {
            Comparison = Comparison.Is;
            paramValue = DBNull.Value;
            query.Constraints.Add(this);
            return query;
        }

        /// <summary>
        /// Determines whether [is between and] [the specified val1].
        /// </summary>
        /// <param name="val1">The val1.</param>
        /// <param name="val2">The val2.</param>
        /// <returns></returns>
        public SqlQuery IsBetweenAnd(object val1, object val2)
        {
            Comparison = Comparison.BetweenAnd;
            StartValue = val1;
            EndValue = val2;
            query.Constraints.Add(this);
            return query;
        }

        public SqlQuery IsEqualTo(object val)
        {
            Comparison = Comparison.Equals;
            ParameterValue = val;
            query.Constraints.Add(this);
            return query;
        }

        public SqlQuery IsNotEqualTo(object val)
        {
            Comparison = Comparison.NotEquals;
            ParameterValue = val;
            query.Constraints.Add(this);
            return query;
        }
    }
}